package com.vaadin.incubator.bugrap.model.facade;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.persistence.config.PersistenceUnitProperties;

import com.vaadin.incubator.bugrap.PasswordGenerator;
import com.vaadin.incubator.bugrap.model.projects.Project;
import com.vaadin.incubator.bugrap.model.projects.ProjectVersion;
import com.vaadin.incubator.bugrap.model.reports.Comment;
import com.vaadin.incubator.bugrap.model.reports.Report;
import com.vaadin.incubator.bugrap.model.reports.ReportPriority;
import com.vaadin.incubator.bugrap.model.reports.ReportResolution;
import com.vaadin.incubator.bugrap.model.reports.ReportStatus;
import com.vaadin.incubator.bugrap.model.reports.ReportType;
import com.vaadin.incubator.bugrap.model.users.Reporter;

/**
 * A utility class to fetch instances from the database
 */
public class FacadeUtil {

	/**
	 * Facade name, this must correspond to what is defined in persistance.xml
	 */

	public static final String FACADE_NAME = "postgresql";
	// public static final String FACADE_NAME = "default";

	public static final String TESTING_FACADE_NAME = "postgresql-test";

	// public static final String TESTING_FACADE_NAME = "default-test";

	/**
	 * Sets a new default facade. If this is not called the the
	 * {@link FacadeUtil.NAME} facade is used.
	 * 
	 * @param name
	 */
	public static void setFacade(String name, String databasePath) {
		Map<String, Object> options = new HashMap<String, Object>();
		options.put(PersistenceUnitProperties.JDBC_URL,
				"jdbc:postgresql:bugrap:" + databasePath);
		FacadeFactory.registerFacade(name, true, options);
	}

	public static void setFacade(String name) {
		FacadeFactory.registerFacade(name, true);
	}

	/**
	 * Get all reports for a project which has not been assigned to a version
	 * 
	 * @param project
	 *            The project to search for
	 * @return Returns a list of reports which have not been assigned to a
	 *         version
	 */
	public static List<Report> getUnassignedReports(Project project) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("proj", project);
		List<Report> unassigned = FacadeFactory
				.getFacade()
				.list("SELECT r FROM Report r WHERE r.project = :proj AND r.version IS NULL",
						params);
		return unassigned;
	}

	/**
	 * Get all reports for a version
	 * 
	 * @param version
	 *            The project version to search for
	 * @return Returns a list of reports which belong to a version
	 */
	public static List<Report> getReportsForVersion(ProjectVersion version) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("proj", version.getProject());
		params.put("ver", version);
		List<Report> reports = FacadeFactory
				.getFacade()
				.list("SELECT r FROM Report r WHERE r.project = :proj AND r.version = :ver",
						params);
		return reports;
	}

	/**
	 * Get a user by username and password
	 * 
	 * @param username
	 *            The username of the user
	 * @param password
	 *            The password of the user
	 * @return Returns a user if the username and password match, else NULL
	 */
	public static Reporter getUser(String username, String password) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("user", username);
		try {
			params.put("password", PasswordGenerator.encrypt(password));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			params.put("password", password);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			params.put("password", password);
		}

		Reporter result = FacadeFactory
				.getFacade()
				.find("SELECT r FROM Reporter r WHERE r.name = :user AND r.password = :password",
						params);

		return result;
	}

	/**
	 * Get all projects.
	 * 
	 * @return Returns all projects
	 */
	public static List<Project> getProjects() {
		return FacadeFactory.getFacade().list(Project.class);
	}

	/**
	 * Get the projects which have not been closed
	 * 
	 * @return A list of projects which has not been closed
	 */
	public static List<Project> getActiveProjects() {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("closed", false);
		List<Project> result = FacadeFactory
				.getFacade()
				.list("SELECT DISTINCT p FROM ProjectVersion as pv JOIN pv.project as p WHERE pv.closed=:closed",
						params);
		return result;
	}

	/**
	 * Get project by id
	 * 
	 * @param id
	 *            The id of the project
	 * @return Returns the project with the corresponding id or NULL if not
	 *         found
	 */
	public static Project getProject(long id) {
		return FacadeFactory.getFacade().find(Project.class, id);
	}

	/**
	 * Get project version by id
	 * 
	 * @param id
	 *            The id of the version
	 * 
	 * @return Returns the project version with the corresponding id or NULL if
	 *         not found
	 */
	public static ProjectVersion getVersion(long id) {
		return FacadeFactory.getFacade().find(ProjectVersion.class, id);
	}

	/**
	 * Get a report by its id
	 * 
	 * @param id
	 *            The id of the report
	 * @return The report which id has been given or NULL if not found
	 */
	public static Report getReport(long id) {
		return FacadeFactory.getFacade().find(Report.class, id);
	}

	/**
	 * Get a report bu his id
	 * 
	 * @param id
	 *            The id of the reporter
	 * @return
	 */
	public static Reporter getReporter(long id) {
		return FacadeFactory.getFacade().find(Reporter.class, id);
	}

	/**
	 * Saves any entity to the database
	 * 
	 * @param entity
	 *            The entity to save
	 */
	public static <A extends AbstractEntity> A store(A entity) {
		return FacadeFactory.getFacade().store(entity);
	}

	/**
	 * Get a reporter by either email or username.
	 * 
	 * @param username
	 *            The username or NULL if searching by email
	 * @param email
	 *            The email or NULL if search by username
	 * @return A reporter which matches the criteria or NULL if not found
	 */
	public static Reporter getReporterByNameOrEmail(String username,
			String email) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("email", email);
		params.put("name", username);
		Reporter result = FacadeFactory
				.getFacade()
				.find("SELECT r FROM Reporter r WHERE r.email = :email or r.name = :name",
						params);
		return result;
	}

	/**
	 * Get all users
	 * 
	 * @return Returns a list of all users
	 */
	public static List<Reporter> getReporters() {
		return FacadeFactory.getFacade().list(Reporter.class);
	}

	/**
	 * Get all project versions
	 * 
	 * @return Returns a list of all project versions
	 */
	public static List<ProjectVersion> getVersions() {
		return FacadeFactory.getFacade().list(ProjectVersion.class);
	}

	/**
	 * Get versions for a project
	 * 
	 * @param project
	 *            The project to get versions for
	 * @return
	 */
	public static List<ProjectVersion> getVersions(Project project) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("proj", project);
		List<ProjectVersion> result = FacadeFactory.getFacade().list(
				"SELECT pv FROM ProjectVersion pv WHERE pv.project=:proj",
				params);
		return result;
	}

	/**
	 * Gets the n latest reports
	 * 
	 * @param amount
	 *            The amount of reports to get
	 * @return Returns the n latest reports
	 */
	public static List<Report> getLatestReports(int amount) {
		List<Report> result = FacadeFactory.getFacade().list(
				"SELECT r FROM Report r ORDER BY r.timestamp DESC", null,
				amount);
		return result;
	}

	public static List<Report> getLatestReports(Project project,
			ProjectVersion version, int amount) {
		Map<String, Object> params = new HashMap<String, Object>();
		StringBuilder query = new StringBuilder("SELECT r FROM Report r WHERE ");
		if (project != null) {
			params.put("proj", project);
			query.append("r.project=:proj ");
		} else {
			query.append("r.project IS NULL ");
		}
		if (version != null) {
			params.put("pv", version);
			query.append("AND r.version=:pv ");
		} else {
			query.append("AND r.version IS NULL ");
		}

		query.append("ORDER BY r.timestamp DESC");

		List<Report> result = FacadeFactory.getFacade().list(query.toString(),
				params, amount);
		return result;
	}

	/**
	 * Counts reports in a certain version and status
	 * 
	 * @param version
	 *            The project version the reports are in
	 * @param status
	 *            The status the reports have
	 * @return The number of reports matching that criteria
	 */
	public static long countReports(ProjectVersion version, ReportStatus status) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("pv", version);
		params.put("status", status);
		return FacadeFactory
				.getFacade()
				.count("SELECT COUNT(r) FROM Report r WHERE r.version=:pv AND r.status=:status",
						params);
	}

	/**
	 * Counts reports in a certain project, version and status
	 * 
	 * @param version
	 *            The project version the reports are in
	 * @param status
	 *            The status the reports have
	 * @return The number of reports matching that criteria
	 */
	public static long countReports(Project project, ProjectVersion version,
			ReportStatus status) {
		Map<String, Object> params = new HashMap<String, Object>();
		StringBuilder query = new StringBuilder(
				"SELECT COUNT(r) FROM Report r WHERE ");
		if (project != null) {
			params.put("proj", project);
			query.append("r.project=:proj ");
		} else {
			query.append("r.project IS NULL ");
		}
		if (version != null) {
			params.put("pv", version);
			query.append("AND r.version=:pv ");
		} else {
			query.append("AND r.version IS NULL ");
		}
		if (status != null) {
			params.put("status", status);
			query.append("AND r.status=:status");
		} else {
			query.append("AND r.status IS NULL ");
		}

		return FacadeFactory.getFacade().count(query.toString(), params);
	}

	/**
	 * Get the reports assigned to a specific user
	 * 
	 * @param assignedTo
	 *            A reporter to who reports are assigned to
	 * @return
	 */
	public static List<Report> getAssignedReports(Project project,
			ProjectVersion version, Reporter assignedTo) {
		Map<String, Object> params = new HashMap<String, Object>();
		StringBuilder query = new StringBuilder("SELECT r FROM Report r WHERE ");
		if (project != null) {
			params.put("proj", project);
			query.append("r.project=:proj ");
		} else {
			query.append("r.project IS NULL ");
		}
		if (version != null) {
			params.put("pv", version);
			query.append("AND r.version=:pv ");
		} else {
			query.append("AND r.version IS NULL ");
		}
		if (assignedTo != null) {
			params.put("uid", assignedTo);
			query.append("AND r.assigned=:uid ");
		} else {
			query.append("AND r.assigned IS NULL ");
		}
		List<Report> result = FacadeFactory.getFacade().list(query.toString(),
				params);
		return result;
	}

	/**
	 * Search for reports
	 * 
	 * @param searchTerm
	 *            The search term to find in the report
	 * @param project
	 *            The project the reports should belong to
	 * @param status
	 *            The status of the report
	 * @param type
	 *            The type of the report
	 * @param resolution
	 *            The resolution of the report
	 * @param priority
	 *            The priority of the report
	 * @param ignoreProject
	 *            Should the project criteria be ignored
	 * @param ignoreStatus
	 *            Should the status criteria be ignored
	 * @param ignoreType
	 *            Should the type criteria be ignored
	 * @param ignoreResolution
	 *            Should the resolution criteria be ignored
	 * @param ignorePriority
	 *            Should the priority criteria be ignored
	 * @return
	 */
	public static List<Report> searchReports(String searchTerm,
			Project project, ProjectVersion version, ReportStatus status,
			ReportType type, ReportResolution resolution,
			ReportPriority priority, boolean ignoreProject,
			boolean ignoreProjectVersion, boolean ignoreStatus,
			boolean ignoreType, boolean ignoreResolution, boolean ignorePriority) {

		if (searchTerm == null) {
			searchTerm = "";
		}

		Map<String, Object> params = new HashMap<String, Object>();
		params.put("term", "%" + searchTerm.toLowerCase() + "%");

		StringBuilder query = new StringBuilder(
				"SELECT r FROM Report r WHERE (lower(r.summary) LIKE :term OR lower(r.description) LIKE :term) ");

		// Project restraint
		if (!ignoreProject) {
			query.append(" AND ");
			if (project == null) {
				query.append("r.project IS NULL ");
			} else {
				params.put("proj", project);
				query.append("r.project = :proj ");
			}
		}

		if (!ignoreProjectVersion) {
			query.append(" AND ");
			if (version == null) {
				query.append("r.version IS NULL ");
			} else {
				params.put("ver", version);
				query.append("r.version = :ver ");
			}
		}

		// Status restraint
		if (!ignoreStatus) {
			query.append(" AND ");
			if (status == null) {
				query.append("r.status IS NULL ");
			} else {
				params.put("status", status);
				query.append("r.status = :status ");
			}
		}

		// Type restraint
		if (!ignoreType) {
			query.append(" AND ");
			if (type == null) {
				query.append("r.type IS NULL ");
			} else {
				params.put("type", type);
				query.append("r.type = :type ");
			}
		}

		// Resolution restraint
		if (!ignoreResolution) {
			query.append(" AND ");
			if (resolution == null) {
				query.append("r.resolution IS NULL ");
			} else {
				params.put("res", resolution);
				query.append("r.resolution = :res ");
			}
		}

		// Priority restraint
		if (!ignorePriority) {
			query.append(" AND ");
			if (priority == null) {
				query.append("r.priority IS NULL ");
			} else {
				params.put("priority", priority);
				query.append("r.priority = :priority ");
			}
		}

		try {
			List<Report> result = FacadeFactory.getFacade().list(
					query.toString(), params);
			return result;
		} catch (Exception e) {
			e.printStackTrace();
			return Collections.EMPTY_LIST;
		}
	}

	public static List<Comment> getComments(Report report) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("rep", report);
		return FacadeFactory.getFacade().list(
				"SELECT c FROM Comment c WHERE c.report=:rep", params);
	}
}
